基于WiFi的Android局域网视频监控的设计与实现(h264编码、解码,surfaceview、socket) 您所在的位置:网站首页 mediacodec 解码到显示 基于WiFi的Android局域网视频监控的设计与实现(h264编码、解码,surfaceview、socket)

基于WiFi的Android局域网视频监控的设计与实现(h264编码、解码,surfaceview、socket)

2024-01-26 05:17| 来源: 网络整理| 查看: 265

一、整体思路

1.摄像头实时捕获视频并进行h264编码 2.接收wifi的视频数据并实时在另一个监控视频手机查看,接收到的数据进行h264解码 3.wifi的数据传输利用socket通信

二、摄像头捕获数据并进行h264编码 摄像头捕获数据

打开相机,设置预览画面,设置监听获取视频流的每一帧:

/** * 打开相机 */ private void openCamera() { camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); //获取相机参数 Camera.Parameters parameters = camera.getParameters(); //获取相机支持的预览的大小 Camera.Size previewSize = getCameraPreviewSize(parameters); int width = previewSize.width; int height = previewSize.height; Log.d("AAA",""+width+" "+height); //设置预览格式(也就是每一帧的视频格式)YUV420下的NV21 parameters.setPreviewFormat(ImageFormat.NV21); //设置预览图像分辨率 parameters.setPreviewSize(width, height); //相机旋转90度 camera.setDisplayOrientation(90); //配置camera参数 camera.setParameters(parameters); try { camera.setPreviewDisplay(holder); } catch (IOException e) { e.printStackTrace(); } //设置监听获取视频流的每一帧 camera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { } }); nv21EncoderH264 = new NV21EncoderH264(width, height); nv21EncoderH264.setEncoderListener(this); //调用startPreview()用以更新preview的surface camera.startPreview(); camera.autoFocus(null); } //设置监听获取视频流的每一帧 camera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { nv21EncoderH264.encoderH264(data); } }); 对数据进行h264编码: public class NV21EncoderH264 { private int width, height; private int frameRate = 30; private MediaCodec mediaCodec; private EncoderListener encoderListener; public NV21EncoderH264(int width, int height) { this.width = width; this.height = height; initMediaCodec(); } private void initMediaCodec() { try { mediaCodec = MediaCodec.createEncoderByType("video/avc"); //height和width一般都是照相机的height和width。 //TODO 因为获取到的视频帧数据是逆时针旋转了90度的,所以这里宽高需要对调 MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", height, width); //描述平均位速率(以位/秒为单位)的键。 关联的值是一个整数 mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5); //描述视频格式的帧速率(以帧/秒为单位)的键。帧率,一般在15至30之内,太小容易造成视频卡顿。 mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); //色彩格式,具体查看相关API,不同设备支持的色彩格式不尽相同 mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); //关键帧间隔时间,单位是秒 mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); //开始编码 mediaCodec.start(); } catch (Exception e) { e.printStackTrace(); } } /** * 将NV21编码成H264 */ public void encoderH264(byte[] data) { //将NV21编码成NV12 byte[] bytes = NV21ToNV12(data, width, height); //视频顺时针旋转90度 byte[] nv12 = rotateNV290(bytes, width, height); try { //拿到输入缓冲区,用于传送数据进行编码 ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); //拿到输出缓冲区,用于取到编码后的数据 ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1); //当输入缓冲区有效时,就是>=0 if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); //往输入缓冲区写入数据 inputBuffer.put(nv12); //五个参数,第一个是输入缓冲区的索引,第二个数据是输入缓冲区起始索引,第三个是放入的数据大小,第四个是时间戳,保证递增就是 mediaCodec.queueInputBuffer(inputBufferIndex, 0, nv12.length, System.nanoTime() / 1000, 0); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); //拿到输出缓冲区的索引 int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); //outData就是输出的h264数据 if (encoderListener != null) { encoderListener.h264(outData); } mediaCodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); } } catch (Throwable t) { t.printStackTrace(); } } /** * 因为从MediaCodec不支持NV21的数据编码,所以需要先讲NV21的数据转码为NV12 */ private byte[] NV21ToNV12(byte[] nv21, int width, int height) { byte[] nv12 = new byte[width * height * 3 / 2]; int frameSize = width * height; int i, j; System.arraycopy(nv21, 0, nv12, 0, frameSize); for (i = 0; i nv12[frameSize + j - 1] = nv21[j + frameSize]; } for (j = 0; j byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2]; // Rotate the Y luma int i = 0; for (int x = 0; x yuv[i] = data[y * imageWidth + x]; i++; } } // Rotate the U and V color components i = imageWidth * imageHeight * 3 / 2 - 1; for (int x = imageWidth - 1; x > 0; x = x - 2) { for (int y = 0; y encoderListener = listener; } public interface EncoderListener { void h264(byte[] data); } } 三、接收到的数据进行h264解码 package com.example.receiver; import android.media.MediaCodec; import android.media.MediaFormat; import android.util.Log; import android.view.Surface; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; public class H264DeCodePlay { private static final String TAG = "zqf-dev"; //使用android MediaCodec解码 private MediaCodec mediaCodec; private Surface surface; MediaCodec.BufferInfo mediaCodecBufferInfo; H264DeCodePlay(Surface surface) { this.surface = surface; initMediaCodec(); } private void initMediaCodec() { try { //创建解码器 H264的Type为 AAC mediaCodec = MediaCodec.createDecoderByType("video/avc"); //创建配置 MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 1080, 1920); //设置解码预期的帧速率【以帧/秒为单位的视频格式的帧速率的键】 mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); //配置绑定mediaFormat和surface mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); mediaCodec.configure(mediaFormat, surface, null, 0); mediaCodecBufferInfo = new MediaCodec.BufferInfo(); mediaCodec.start(); Log.e(TAG, "创建解码成功"); } catch (IOException e) { e.printStackTrace(); //创建解码失败 Log.e(TAG, "创建解码失败"); } } /** * 解码播放 */ void decodePlay() { } public void decode(byte[] buf) { try { //获取MediaCodec的输入流 // ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); //设置解码等待时间,0为不等待,-1为一直等待,其余为时间单位 int inputBufferIndex = mediaCodec.dequeueInputBuffer(0); //填充数据到输入流 if (inputBufferIndex >= 0) { ByteBuffer inputBuffer; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex); } else { //兼容安卓5.0以下,如不需要可以删掉 inputBuffer = mediaCodec.getInputBuffers()[inputBufferIndex]; } // inputBuffer.put(buf); //mediaCodec.queueInputBuffer(inputBufferIndex, 0,buf.length , System.nanoTime(), 0); inputBuffer.put(buf, 0, buf.length); mediaCodec.queueInputBuffer(inputBufferIndex, 0, buf.length, System.nanoTime() , 0); } //解码数据到surface,实际项目中最好将以下代码放入另一个线程,不断循环解码以降低延迟 int outputBufferIndex = mediaCodec.dequeueOutputBuffer(mediaCodecBufferInfo, 0); if (outputBufferIndex >= 0) { mediaCodec.releaseOutputBuffer(outputBufferIndex, true); } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { //此处可以或得到视频的实际分辨率,用以修正宽高比 //fixHW(); } } catch (IllegalStateException e) { e.printStackTrace(); } } //读取一帧数据 private int findByFrame(byte[] bytes, int start, int totalSize) { for (int i = start; i return i; } } return -1; } private byte[] getBytes(String videoPath) throws IOException { InputStream is = new DataInputStream(new FileInputStream(new File(videoPath))); int len; int size = 1024; byte[] buf; ByteArrayOutputStream bos = new ByteArrayOutputStream(); buf = new byte[size]; while ((len = is.read(buf, 0, size)) != -1) bos.write(buf, 0, len); buf = bos.toByteArray(); return buf; } } 四、wifi的数据传输利用socket通信 由于H264是一种帧间编码,只记录每一帧之间的变化,后期解码的时候只要在上一帧基础上算出变化就可以了。并且我们监听完视频流后就开始对每一帧进行h264编码,如果这时socket还没连接好,接受放就无法收到第一帧数据,从而无法对h264的数据进行解码,造成视频数据丢失,所以我们应该在socket链接后再对视频流进行监听,再进行编码、发送数据。发送端的socket的连接: class SocketConnectThread extends Thread{ public void run(){ Log.e("info", "run: ============线程启动" ); try { //等待客户端的连接,Accept会阻塞,直到建立连接, //所以需要放在子线程中运行。 mSocket = mServerSocket.accept(); os = new DataOutputStream(mSocket.getOutputStream()); mSocket.setSendBufferSize(1000000); //设置监听获取视频流的每一帧 camera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { nv21EncoderH264.encoderH264(data); } }); } catch (Exception e) { e.printStackTrace(); return; } Log.e("info","connect success========================================"); } } 编码成功的回调: //编码成功的回调 @Override public void h264(byte[] data) { Log.e("TAG1", data.length+ ""); try { send(data); } catch (InterruptedException e) { e.printStackTrace(); } } 先在子节流中写入一帧数据的字节数组大小,这样接受端就能提前创建好对应大小的字节数组: public void send( byte[] data) throws InterruptedException { Thread thread=new Thread() { @Override public void run() { try { // socket.getInputStream() if(mSocket!=null&&isSending) { os.writeInt(data.length); os.flush(); os.write(data); os.flush(); // writer.writeUTF(str); // 写一个UTF-8的信息 } } catch (IOException e) { socketConnectThread=new SocketConnectThread(); socketConnectThread.start(); e.printStackTrace(); } } }; executorService.submit(thread); }

接收端接受数据并进行解码,在进行测试的时候,由于每次发送的字节数组过大,read()可能没办法每次都能读完,这里我们再加个循环保证接收到的数据是完整的:

new Thread(){ @Override public void run() { DataInputStream reader= new DataInputStream(mInStream); try { while (true) { // 获取读取流 int length=reader.readInt(); byte[] bytes =new byte[length]; System.out.println("*等待客户端输入*"); int index = 0; int len = 0; while(index index += len; }else { break; } } Log.e("TAG1", length+ " "+bytes.length); h264DeCodePlay.decode(bytes); } } catch (IOException e) { e.printStackTrace(); } } }.start();

本文demo(发送端):https://github.com/gujunhe/client



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有